package tools

import (
	"archive/zip"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"dpm/internal/config"

	"gitee.com/MM-Q/colorlib"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
)

// 初始化彩色打印库
var Cl = colorlib.NewColorLib()

var (
	// 定义支持的操作系统列表
	supportedOS = []string{"linux", "windows", "darwin", "freebsd", "openbsd", "netbsd"}
	// 定义支持的架构列表
	supportedArch = []string{"amd64", "arm64", "386", "arm"}
)

// PathInfo 是一个结构体，用于封装路径的信息
type PathInfo struct {
	Path    string      // 路径
	Exists  bool        // 是否存在
	IsFile  bool        // 是否为文件
	IsDir   bool        // 是否为目录
	Size    int64       // 文件大小（字节）
	Mode    os.FileMode // 文件权限
	ModTime time.Time   // 文件修改时间
}

// InitConfig 初始化配置文件。
// 参数 filename 是要创建的配置文件的名称，包含路径信息。
// 参数 data 是要写入配置文件的内容。
// 返回值是一个错误对象，如果操作过程中出现错误，则返回相应的错误信息。
func InitConfig(filename string, data string) error {
	// 检查指定的配置文件是否已经存在
	if _, err := os.Stat(filename); err == nil {
		// 如果文件已存在，返回错误信息，提示用户删除或重命名文件后重试
		return fmt.Errorf("当前目录已经存在配置文件: %s, 请在删除或者重命名该文件后重试。", filename)
	}

	// 尝试将数据写入指定的配置文件
	if err := os.WriteFile(filename, []byte(data), 0644); err != nil {
		// 如果写入文件时发生错误，返回错误信息
		return fmt.Errorf("创建配置文件时发生错误: %s", err)
	}
	// 打印成功信息，提示配置文件初始化成功
	Cl.PrintSuccessf("初始化配置创建成功: %s", filename)
	// 操作成功，返回 nil
	return nil
}

// CheckAndCreateDir 检查指定目录是否存在，如果不存在且 create 为 true 则创建该目录。
// 参数 dir 是要检查或创建的目录路径。
// 参数 create 是一个布尔值，指示在目录不存在时是否创建该目录。
// 返回值是一个错误对象，如果操作过程中出现错误，则返回相应的错误信息。
func CheckAndCreateDir(dir string, create bool) error {
	// 调用 os.Stat 函数检查目录是否存在
	_, err := os.Stat(dir)

	// 检查目录是否不存在
	if os.IsNotExist(err) {
		// 如果目录不存在且 create 为 true，则创建目录
		if create {
			// 使用 os.MkdirAll 递归创建目录，权限设置为 os.ModePerm
			if err := os.MkdirAll(dir, os.ModePerm); err != nil {
				// 如果创建目录失败，返回错误信息
				return fmt.Errorf("创建目录时发生了错误: %s", err)
			}
			// 创建目录成功，返回 nil
			return nil
		} else {
			// 如果目录不存在且 create 为 false，返回错误信息
			return fmt.Errorf("目录 '%s' 不存在", dir)
		}
	} else if err != nil {
		// 如果检查目录时出现除目录不存在之外的错误，返回错误信息
		return fmt.Errorf("检查目录时发生了错误: %s", err)
	}

	// 目录存在且无错误，返回 nil
	return nil
}

// ValidateTarget 验证构建目标的操作系统和架构是否有效。
// 参数 target 是要验证的构建目标，包含操作系统和架构信息。
// 返回值是一个错误对象，如果验证过程中出现错误，则返回相应的错误信息。
func ValidateTarget(target config.BuildTarget) error {
	// 检查操作系统名称是否为小写
	if strings.ToLower(target.OS) != target.OS {
		return errors.New("目标操作系统名称必须为小写")
	}

	// 标记操作系统是否有效
	isOSValid := false
	// 遍历支持的操作系统列表，检查目标操作系统是否在列表中
	for _, os := range supportedOS {
		if target.OS == os {
			isOSValid = true
			break
		}
	}
	// 如果目标操作系统不在支持列表中，返回错误信息
	if !isOSValid {
		return fmt.Errorf("不支持的目标操作系统: %s, 支持的操作系统列表: %s", target.OS, strings.Join(supportedOS, ", "))
	}

	// 检查架构名称是否为小写
	if strings.ToLower(target.Arch) != target.Arch {
		return errors.New("目标架构名称必须为小写")
	}

	// 标记架构是否有效
	isArchValid := false
	// 遍历支持的架构列表，检查目标架构是否在列表中
	for _, arch := range supportedArch {
		if target.Arch == arch {
			isArchValid = true
			break
		}
	}
	// 如果目标架构不在支持列表中，返回错误信息
	if !isArchValid {
		return fmt.Errorf("不支持的目标架构: %s, 支持的架构列表: %s", target.Arch, strings.Join(supportedArch, ", "))
	}

	// 验证通过，返回 nil
	return nil
}

// CheckPath 检查给定路径的信息
func CheckPath(path string) (PathInfo, error) {
	// 创建一个 PathInfo 结构体
	var info PathInfo

	// 清理路径，确保没有多余的斜杠
	path = filepath.Clean(path)

	// 设置路径
	info.Path = path

	// 使用 os.Stat 获取文件状态
	fileInfo, err := os.Stat(path)
	if err != nil {
		if os.IsNotExist(err) {
			// 如果路径不存在, 则直接返回
			info.Exists = false
			return info, fmt.Errorf("路径 '%s' 不存在，请检查路径是否正确: %s", path, err)
		} else {
			return info, fmt.Errorf("无法访问路径 '%s': %s", path, err)
		}
	}

	// 路径存在，填充信息
	info.Exists = true                // 标记路径存在
	info.IsFile = !fileInfo.IsDir()   // 通过取反判断是否为文件，因为 IsDir 返回 false 表示是文件
	info.IsDir = fileInfo.IsDir()     // 直接使用 IsDir 方法判断是否为目录
	info.Size = fileInfo.Size()       // 获取文件大小
	info.Mode = fileInfo.Mode()       // 获取文件权限
	info.ModTime = fileInfo.ModTime() // 获取文件的最后修改时间

	// 返回路径信息结构体
	return info, nil
}

// DecodeBytes 函数用于解码字节数组, 支持UTF-8和GBK编码
func DecodeBytes(data []byte) (string, error) {
	// 检查数据是否为有效的UTF-8编码
	if utf8.Valid(data) {
		// 如果是有效的UTF-8编码, 直接将字节数组转换为字符串并返回
		return string(data), nil
	}
	// 如果不是有效的UTF-8编码, 尝试使用GBK编码进行解码
	reader := transform.NewReader(
		// 使用bytes.NewReader将字节数组转换为io.Reader
		bytes.NewReader(data),
		// 使用simplifiedchinese.GBK.NewDecoder创建一个GBK解码器
		simplifiedchinese.GBK.NewDecoder(),
	)
	// 使用io.ReadAll读取解码后的数据
	decodedData, err := io.ReadAll(reader)
	if err != nil {
		// 如果读取过程中发生错误，返回错误信息
		return "", fmt.Errorf("解码失败，尝试的编码: GBK。错误信息: %s", err)
	}
	// 将解码后的字节数组转换为字符串并返回
	return string(decodedData), nil
}

// ExecCmds 执行一系列命令
// 参数 cmds 是一个二维字符串切片，每个子切片代表一个命令及其参数
// 参数 msg 是一个字符串，用于描述这些命令所属的阶段
func ExecCmds(cmds [][]string, msg string) error {
	// 检查命令列表是否为空
	if len(cmds) == 0 {
		// 如果为空，打印提示信息并返回
		Cl.Greenf("- 未设置 %s 阶段的命令，跳过...", msg)
		return nil
	}

	// 打印开始执行命令的信息
	Cl.Greenf("- 开始执行 %s 阶段的命令，共 %d 条...", msg, len(cmds))

	// 遍历命令列表
	for i, cmd := range cmds {
		// 打印正在执行的命令信息
		Cl.Greenf("%d/%d: 正在执行命令: %v", i+1, len(cmds), cmd)

		// 创建一个命令对象，用于执行命令
		run := exec.Command(cmd[0], cmd[1:]...)

		// 捕获子进程的标准输出和标准错误
		var stdout, stderr bytes.Buffer
		run.Stdout = &stdout
		run.Stderr = &stderr

		// 覆盖环境变量, 使用当前进程的环境变量
		run.Env = os.Environ()

		// 执行命令
		if err := run.Run(); err != nil {
			// 如果命令失败，记录错误信息并返回
			Cl.PrintErrorf("- 执行失败: 命令 %v", cmd)
			// 尝试解码并打印标准输出
			if stdout, err := DecodeBytes(stdout.Bytes()); err == nil && stdout != "" {
				Cl.PrintSuccessf("- 标准输出: %s", stdout)
			}
			// 尝试解码并打印标准错误
			if stderr, err := DecodeBytes(stderr.Bytes()); err == nil && stderr != "" {
				Cl.PrintErrorf("- 错误输出: %s", stderr)
			}
			// 打印错误信息
			Cl.PrintErrorf("错误信息: %v", err)

			// 返回错误信息，包含命令执行阶段和索引
			return fmt.Errorf("在执行 %s 阶段的第 %d/%d 条命令时失败: %s", msg, i+1, len(cmds), err)
		}

		// 尝试解码并打印标准输出
		if decodedOutput, err := DecodeBytes(stdout.Bytes()); err == nil && decodedOutput != "" {
			Cl.PrintErrorf("标准输出: %s", decodedOutput)
		} else if decodedOutput == "" {
			Cl.PrintSuccess("命令执行成功!")
		}

		// 尝试解码并打印标准错误
		if decodedError, err := DecodeBytes(stderr.Bytes()); err == nil && decodedError != "" {
			Cl.PrintErrorf("错误输出: %s", decodedError)
		}
	}

	// 打印命令执行完成的信息
	Cl.Greenf("- %s 阶段的命令执行完成！", msg)
	println()
	return nil
}

// SetupEnv 设置环境变量
// 输入 envs 是一个键值对形式的环境变量映射，键为环境变量名，值为环境变量值
func SetupEnv(envs map[string]string) error {
	Cl.Green("[环境配置]")

	// 检查传入的环境变量映射是否为空
	if len(envs) == 0 {
		// 如果为空，打印提示信息并返回
		Cl.Yellow(">>> 未设置环境变量, 跳过...\n")
		return nil
	}

	// 获取环境变量映射的长度
	envsLen := len(envs)

	// 计数器
	jd := 0

	// 遍历环境变量映射
	for key, value := range envs {
		// 检查环境变量是否已存在
		if os.Getenv(key) != "" {
			Cl.Yellowf("%d/%d: 环境变量 %s 已存在，将被覆盖", jd, envsLen, key)
		}

		// 设置环境变量
		if err := os.Setenv(key, value); err != nil {
			// 如果设置环境变量失败，打印错误信息并返回
			return fmt.Errorf("设置环境变量失败: %s", err)
		}

		// 计数器加1
		jd++

		// 打印设置的环境变量信息
		Cl.Greenf("%d/%d: 已设置 %s=%s \342\234\224", jd, envsLen, key, value)
	}

	println()
	return nil
}

// CheckOSAndArch 检查当前平台系统和架构是否符合构建要求
func CheckOSAndArch(platform config.Platform, name string) error {
	var (
		isArchPermitted = false // 是否允许当前架构
		isOSPermitted   = false // 是否允许当前操作系统
	)

	// 检查当前平台系统是否符合构建要求
	for _, os := range platform.OS {
		expectOs := strings.ToLower(os)            // 将期望的操作系统转换为小写
		currentOs := strings.ToLower(runtime.GOOS) // 获取当前操作系统并转换为小写

		// 检查当前操作系统是否匹配
		if expectOs == currentOs {
			isOSPermitted = true
			break
		}
	}

	// 如果当前平台系统不符合构建要求, 则打印期望的平台系统和架构
	if !isOSPermitted {
		Cl.PrintErrorf("期望的平台系统: %s", platform.OS)
	}

	// 检查当前平台架构是否符合构建要求
	for _, arch := range platform.Arch {
		expectArch := strings.ToLower(arch)            // 将期望的架构转换为小写
		currentArch := strings.ToLower(runtime.GOARCH) // 获取当前操作系统并转换为小写

		// 检查当前架构是否匹配
		if expectArch == currentArch {
			isArchPermitted = true
			break
		}
	}
	// 如果当前平台架构不符合构建要求, 则打印期望的平台系统和架构
	if !isArchPermitted {
		Cl.PrintErrorf("期望的平台架构: %s", platform.Arch)
	}

	// 如果当前平台系统和架构都不符合构建要求, 则打印错误信息并返回
	if !isOSPermitted || !isArchPermitted {
		// 如果当前平台系统和架构都不符合构建要求, 则打印错误信息并返回
		return fmt.Errorf("当前平台 %s/%s 不支持构建 %s", runtime.GOOS, runtime.GOARCH, name)
	}

	return nil
}

// LoadConfig 加载配置文件
func LoadConfig(cfgF string, defaultPath string) (string, error) {
	// 加载配置文件路径, 优先加载命令行参数, 其次加载默认值
	var configPath string

	// 1、先加载命令行参数, 如果有配置文件路径，则使用配置文件路径
	if cfgF != "" {
		configPath = cfgF
		if _, err := CheckPath(configPath); err != nil {
			return "", fmt.Errorf("命令行参数配置的配置文件路径 %s 不存在, 请检查命令行参数配置", configPath)
		}

		// 如果命令行参数配置的配置文件路径存在, 则加载命令行参数配置的配置文件路径
		return configPath, nil
	}

	// 2、如果命令行参数没有配置文件路径, 则加载指定默认配置文件路径
	if _, err := CheckPath(defaultPath); err != nil {
		return "", fmt.Errorf("默认配置文件 %s 不存在, 请使用 -c 指定配置文件路径, 或使用 -h 查看帮助信息", defaultPath)
	} else {
		// 如果默认配置文件存在, 则加载默认配置文件
		configPath = defaultPath
		return configPath, nil
	}
}

// ReadConfig 读取加载的配置文件
// 输入 cfgF 命令行参数接收的配置文件路径
func ReadConfigOrDefault(cfgF string, defaultPath string) ([]byte, error) {
	// 调用 loadConfig 函数加载配置文件路径
	configPath, err := LoadConfig(cfgF, defaultPath)
	if err != nil {
		return nil, err
	}

	// 读取配置文件内容
	configBytes, err := os.ReadFile(configPath)
	if err != nil {
		return nil, fmt.Errorf("读取配置文件失败: %s", err)
	}

	return configBytes, nil
}

// ReadConfig 读取加载的配置文件
// 输入 cfgF 命令行参数接收的配置文件路径
func ReadConfig(configPath string) ([]byte, error) {
	// 检查传入的配置文件路径是否为空
	if _, err := CheckPath(configPath); err != nil {
		return nil, fmt.Errorf("配置文件路径 %s 不存在", configPath)
	}

	// 检查传入的路径是否存在
	if _, err := CheckPath(configPath); err != nil {
		return nil, fmt.Errorf("配置文件路径 %s 不存在", configPath)
	}

	// 读取配置文件内容
	configBytes, err := os.ReadFile(configPath)
	if err != nil {
		return nil, fmt.Errorf("读取配置文件失败: %s", err)
	}

	return configBytes, nil
}

// MergeStringFlags 函数用于合并长选项和短选项，返回一个字符串和一个错误。
// 如果同时指定了长选项和短选项，则返回一个错误。
// 如果只指定了一个选项，则返回该选项的值。
// 如果两个选项都未指定，则返回空字符串。
func MergeStringFlags(longPtr, shortPtr string) (string, error) {
	// 检查长选项和短选项是否同时不为空
	if longPtr != "" && shortPtr != "" {
		// 如果同时指定了长选项和短选项，则返回一个错误
		return "", errors.New("不能同时指定长选项和短选项")
	}

	// 如果长选项不为空, 则返回长选项
	if longPtr != "" {
		return longPtr, nil
	}

	// 如果短选项不为空, 则返回短选项
	if shortPtr != "" {
		return shortPtr, nil
	}

	// 如果长选项和短选项都为空, 则返回空字符串
	return "", nil
}

// CheckGit 检查是否安装了 Git
func CheckGit() error {
	cmd := exec.Command("git", "--version")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("未检测到 Git 安装，请确保 Git 已正确安装并添加到系统 PATH 中")
	}
	return nil
}

// CheckGitRepo 检查当前目录是否为 Git 仓库
func CheckGitRepo() error {
	// 检查是否安装了 Git
	if err := CheckGit(); err != nil {
		return err
	}

	// 检查当前目录是否为 Git 仓库
	cmd := exec.Command("git", "rev-parse", "--is-inside-work-tree")
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("当前目录不是 Git 仓库, 请先初始化 Git 仓库")
	}
	if strings.TrimSpace(out.String()) != "true" {
		return fmt.Errorf("当前目录不是 Git 仓库, 请先初始化 Git 仓库")
	}

	// 如果当前目录是 Git 仓库, 则返回 nil
	return nil
}

// GetGitVersion 获取 Git 仓库版本号
func GetGitVersion() (string, error) {
	// 检查当前目录是否为 Git 仓库
	if err := CheckGitRepo(); err != nil {
		return "", err
	}

	cmd := exec.Command("git", "describe", "--tags", "--always", "--dirty")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("获取 Git 版本号失败: %s", err)
	}
	return strings.TrimSpace(string(output)), nil
}

// GetGitCommit 获取 Git 仓库提交哈希值
func GetGitCommit() (string, error) {
	// 检查当前目录是否为 Git 仓库
	if err := CheckGitRepo(); err != nil {
		return "", err
	}

	cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("获取 Git 提交哈希值失败: %s", err)
	}
	return strings.TrimSpace(string(output)), nil
}

// GetBuildTime 获取构建时间
func GetBuildTime() (string, error) {
	// 检查当前目录是否为 Git 仓库
	if err := CheckGitRepo(); err != nil {
		return "", err
	}

	// 获取零时区的当前时间
	now := time.Now().UTC()

	// 格式化时间
	return now.Format("2006-01-02T15:04:05Z"), nil
}

// GetGitCommitTime 获取 Git 仓库提交时间
func GetGitCommitTime() (string, error) {
	// 检查当前目录是否为 Git 仓库
	if err := CheckGitRepo(); err != nil {
		return "", err
	}

	cmd := exec.Command("git", "log", "-1", `--pretty=format:"%cd"`, "--date=iso")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("获取 Git 提交时间失败: %s", err)
	}

	tmpTime, err := time.Parse("2006-01-02 15:04:05 -0700", strings.Trim(strings.TrimSpace(string(output)), `"`))
	if err != nil {
		return "", fmt.Errorf("解析 Git 提交时间失败: %s", err)
	}

	// 格式化时间
	return tmpTime.UTC().Format(time.RFC3339), nil
}

// GetGitTreeState 获取 Git 仓库树状状态
func GetGitTreeState() (string, error) {
	// 检查当前目录是否为 Git 仓库
	if err := CheckGitRepo(); err != nil {
		return "", err
	}

	cmd := exec.Command("git", "status", "-s")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("获取 Git 状态失败: %s", err)
	}

	// 如果 Git 状态输出不为空，则表示有未提交的更改，返回 "dirty"
	if strings.TrimSpace(string(output)) != "" {
		return "dirty", nil
	}

	// 如果 Git 状态输出为空，则表示没有未提交的更改，返回 "clean"
	return "clean", nil
}

// CreateZip 创建一个 zip 格式的压缩文件
// source 是要压缩的源文件或目录路径
// target 是压缩后的目标文件路径
// 返回值：如果操作成功，返回 nil；如果出现错误，返回相应的错误信息
func CreateZip(source, target string) error {
	// 创建目标 zip 文件
	zipFile, err := os.Create(target)
	// 检查创建文件时是否出错
	if err != nil {
		// 如果出错，返回错误信息
		return err
	}
	// 确保在函数结束时关闭目标 zip 文件
	defer zipFile.Close()

	// 创建一个 zip 写入器，用于将文件写入目标 zip 文件
	zipWriter := zip.NewWriter(zipFile)
	// 确保在函数结束时关闭 zip 写入器
	defer zipWriter.Close()

	// 遍历源目录及其子目录，对每个文件或目录执行以下操作
	return filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
		// 检查遍历过程中是否出错
		if err != nil {
			// 如果出错，返回错误信息
			return err
		}

		// 创建 zip 文件头，包含文件的元数据
		header, err := zip.FileInfoHeader(info)
		// 检查创建文件头时是否出错
		if err != nil {
			// 如果出错，返回错误信息
			return err
		}

		// 去除文件路径中的源目录前缀，得到相对路径
		header.Name = strings.TrimPrefix(path, filepath.Dir(source)+"/")
		// 如果是目录
		if info.IsDir() {
			// 在目录名后添加斜杠
			header.Name += "/"
		} else {
			// 如果是文件，使用 Deflate 压缩方法
			header.Method = zip.Deflate
		}

		// 根据文件头创建一个写入器，用于将文件内容写入 zip 文件
		writer, err := zipWriter.CreateHeader(header)
		// 检查创建写入器时是否出错
		if err != nil {
			// 如果出错，返回错误信息
			return err
		}

		// 如果不是目录
		if !info.IsDir() {
			// 打开文件
			file, err := os.Open(path)
			// 检查打开文件时是否出错
			if err != nil {
				// 如果出错，返回错误信息
				return err
			}
			// 确保在函数结束时关闭文件
			defer file.Close()
			// 将文件内容复制到 zip 写入器
			_, err = io.Copy(writer, file)
			// 返回复制操作的结果，如果出错则返回错误信息
			return err
		}
		// 如果是目录，不进行文件内容复制，返回 nil 表示操作成功
		return nil
	})
}

// FormatValue 函数用于格式化字符串值。
// 如果传入的 value 为空字符串，则返回格式化后的 defaultValue。
// 否则，返回格式化后的 value。
// 格式化的规则是在字符串两侧添加双引号。
func FormatValue(value string, defaultValue string) string {
	// 检查传入的 value 是否为空字符串
	if value == "" {
		// 如果 value 为空，返回格式化后的 defaultValue
		return fmt.Sprintf(`"%s"`, defaultValue)
	}
	// 如果 value 不为空，返回格式化后的 value
	return fmt.Sprintf(`"%s"`, value)
}

// GetProjectVersion 获取项目版本号, 如果获取失败, 则返回默认版本号
func GetProjectVersion() string {
	version := "v0.0.1" // 默认版本号，用于在无法获取 Git 版本号时使用
	// 检查当前目录是否为 Git 仓库
	if err := CheckGitRepo(); err != nil {
		return version // 如果不是 Git 仓库，返回默认版本号
	}

	// 构建一个 Git 命令，用于获取当前仓库的版本号
	// `git describe --tags --always --dirty` 命令解释：
	// --tags: 使用最近的标签作为版本号的基础
	// --always: 如果没有标签，则使用提交的哈希值
	// --dirty: 如果工作区有未提交的更改，则在版本号后添加 "-dirty" 标记
	gitCmd := exec.Command("git", "describe", "--tags", "--always", "--dirty")
	// 执行 Git 命令并获取输出
	output, err := gitCmd.CombinedOutput()
	if err != nil {
		// 如果执行命令失败，打印错误信息并返回默认版本号
		Cl.PrintError(err)
		Cl.PrintError("获取 Git 版本号失败，请检查 Git 是否安装（运行 `git --version` 检查），或当前目录是否为 Git 仓库。")
		return version
	}

	return strings.TrimSpace(string(output)) // 直接返回处理后的版本号
}

// IsValidFilePermission 检查文件权限字符串是否合法
// 权限字符串应为三位八进制数字，例如 "0755"
func IsValidFilePermission(permission string) bool {
	// 去除字符串中的空格和换行符
	permission = strings.TrimSpace(permission)

	// 检查权限字符串是否为三位八进制数字
	if len(permission) != 4 || permission[0] != '0' {
		return false
	}

	// 遍历每一位数字，检查是否在0到7之间
	for _, char := range permission[1:] {
		if char < '0' || char > '7' {
			return false
		}
	}

	// 检查权限是否符合常见的文件权限范围
	// 通常文件权限的范围是000到777
	permValue, err := strconv.ParseInt(permission, 8, 32)
	if err != nil {
		return false
	}
	if permValue < 0 || permValue > 0777 {
		return false
	}

	return true
}

// ProcessCommands 处理命令列表
func ProcessCommands(commands [][]string, action string) error {
	if len(commands) == 0 {
		Cl.Greenf("- 本次未配置需要执行的%s", action)
		return nil
	}

	Cl.Greenf("[开始%s]", action)
	return ExecCmds(commands, action)
}

// IsNotEmpty 检查字符串是否不为空
func IsNotEmpty(s string, msg string) (string, error) {
	// 去除字符串两端的空格
	s = strings.TrimSpace(s)

	// 检查字符串是否为空
	if s == "" {
		return "", fmt.Errorf("%s不能为空", msg)
	}

	return s, nil
}

// ExecCmd 执行命令
func ExecCmd(cmd []string, msg string, isPrint bool) error {
	// 检查命令是否为空
	if len(cmd) == 0 {
		return fmt.Errorf("%s 命令不能为空", msg)
	}

	// 构建命令
	command := exec.Command(cmd[0], cmd[1:]...)

	// 执行命令
	output, err := command.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%s: 命令执行失败: %s\n命令输出: %s", msg, err, string(output))
	}

	// 打印命令输出
	if isPrint {
		fmt.Println(string(output))
	}

	// 返回 nil 表示命令执行成功
	return nil
}

// ConvertOctalStringToMode 将4位八进制字符串权限转换为 os.FileMode 类型
// 参数：
//   octalStr - 4位八进制字符串，例如 "0755" 或 "0644"
// 返回：
//   os.FileMode - 转换后的文件权限
//   error - 如果输入不是合法的4位八进制数，返回错误
func ConvertOctalStringToMode(octalStr string) (os.FileMode, error) {
	// 检查输入是否为4位
	if len(octalStr) != 4 {
		return 0, fmt.Errorf("输入的权限字符串长度必须为4位")
	}

	// 尝试将字符串解析为八进制数
	mode, err := strconv.ParseUint(octalStr, 8, 32)
	if err != nil {
		return 0, fmt.Errorf("输入的权限字符串不是合法的八进制数: %w", err)
	}

	// 将 uint 转换为 os.FileMode
	return os.FileMode(mode), nil
}